#include "main.h"

int main( int argc, char *argv[] )
{	
	srand( time(NULL) );

	glutInit( &argc, argv );
	InitGraphics();
	InitLists();
	Reset();
	InitGlui();
	glutMainLoop();
	return 0;
}

void Animate( void )
{
	glutSetWindow( MainWindow );
	glFinish();

	time0 = omp_get_wtime( );

	for(int i = 0; i < NUMPARTICLES; i++ ) {
		updateParticle(i);

		if (points[i].y < -5)
			genParticle(i, false);
	}

	time1 = omp_get_wtime( );
	compTimes[compNum] = time1 - time0;
	compNum++;
	if(compNum >= TIME_COUNT)
	{
		LogTimeOut();
	}

	time0 = omp_get_wtime( );

	glBindBuffer( GL_ARRAY_BUFFER, hPobj );
	glBufferSubData( GL_ARRAY_BUFFER, 0, 4 * NUMPARTICLES * sizeof(float), (void*)points);
	
	time1 = omp_get_wtime( );
	compTimes[compNum] = time1 - time0;
	compNum++;
	if(compNum >= TIME_COUNT)
	{
		LogTimeOut();
	}

	glutSetWindow( MainWindow );
	glutPostRedisplay();
}

// Buttons callback
void Buttons( int id )
{
	switch( id )
	{
		case GO:
			startTime = omp_get_wtime();
			GLUI_Master.set_glutIdleFunc( Animate );
			break;
			
		case RESET:
			Reset();
			GLUI_Master.set_glutIdleFunc( NULL );
			Glui->sync_live();
			glutSetWindow( MainWindow );
			glutPostRedisplay();
			break;
			
		case QUIT:
			Glui->close();
			glutSetWindow( MainWindow );
			glFinish();
			glutDestroyWindow( MainWindow );
			exit( 0 );
			break;
			
		default:
			fprintf( stderr, "Don't know what to do with Button ID %d\n", id );
	}
	
}


// Draw the scene
void Display( void )
{
	time0 = omp_get_wtime();

	GLsizei vx, vy, v;		// viewport dimensions
	GLint xl, yb;		// lower-left corner of viewport
	GLfloat scale2;		// real glui scale factor
	
	// Set which window we want to do the graphics into
	glutSetWindow( MainWindow );
		
	// Erase the background:
	glDrawBuffer( GL_BACK );
	glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
	glEnable( GL_DEPTH_TEST );
	
	// Specify shading to be flat
	glShadeModel( GL_FLAT );
	
	// Set the viewport to a square centered in the window
	vx = glutGet( GLUT_WINDOW_WIDTH );
	vy = glutGet( GLUT_WINDOW_HEIGHT );
	v = vx < vy ? vx : vy;			// minimum dimension
	xl = ( vx - v ) / 2;
	yb = ( vy - v ) / 2;
	glViewport( xl, yb,  v, v );
	
	// Set the viewing volume
	glMatrixMode( GL_PROJECTION );
	glLoadIdentity();
	if( WhichProjection == ORTHO )
		glOrtho(-2., 2., -2., 2., 0.1, 100.);
		//glOrtho( -300., 300.,  -300., 300., 0.1, 1000. );
	else
		gluPerspective(60., 1., 0.1, 100.);
	
	// Place the objects into the scene
	glMatrixMode( GL_MODELVIEW );
	glLoadIdentity();
	//gluLookAt( 0., 0., 400.,     0., 0., 0.,     0., 1., 0. );
	
	glTranslatef( (GLfloat)TransXYZ[0], (GLfloat)TransXYZ[1], -(GLfloat)TransXYZ[2] );
	glTranslatef(0,0,-4);
	glRotatef( (GLfloat)Yrot, 0., 1., 0. );
	glRotatef( (GLfloat)Xrot, 1., 0., 0. );
	glMultMatrixf( (const GLfloat *) RotMatrix );
	//glScalef( (GLfloat)Scale, (GLfloat)Scale, (GLfloat)Scale );
	scale2 = 1. + Scale2;		// because glui translation starts at 0.
	if( scale2 < MINSCALE )
		scale2 = MINSCALE;
	//glScalef( (GLfloat)scale2, (GLfloat)scale2, (GLfloat)scale2 );	
	
	glDisable( GL_FOG );

	// Draw the points	
	glBindBuffer( GL_ARRAY_BUFFER, hPobj );
	glVertexPointer( 4, GL_FLOAT, 0, (void *)0);
	glEnableClientState( GL_VERTEX_ARRAY );

	glBindBuffer( GL_ARRAY_BUFFER, hCobj );
	glColorPointer( 4, GL_FLOAT, 0, (void *)0 );
	glEnableClientState( GL_COLOR_ARRAY );

	glPointSize( 3. );
	glDrawArrays( GL_POINTS, 0, NUMPARTICLES );
	//glPointSize( 1. );

	glDisableClientState( GL_VERTEX_ARRAY );
	glDisableClientState( GL_COLOR_ARRAY );
	glBindBuffer( GL_ARRAY_BUFFER, 0);

	glFlush();
	glFinish();

	time1 = omp_get_wtime( );
	compTimes[compNum] = time1 - time0;
	compNum++;
	if(compNum >= TIME_COUNT)
	{
		LogTimeOut();
	}

	glutSwapBuffers();
}

string genFileName() {
    ostringstream oss;
    oss << "nocl-" << NUMPARTICLES << "_" << time(NULL) << ".csv";
    return oss.str();
}

void LogTimeOut()
{
#ifndef WIN32
# ifndef __APPLE__
	cout << "Error: output is currently only supported in Windows and OS X" << endl;
	cin.get();
	Quit();
# endif
#endif
    
    string outputParent = "native";
    string outputDir = "output";

	endTime = omp_get_wtime();

	ofstream myfile;


#ifdef WIN32
   	ostringstream oss;
	char buffer[MAX_PATH]; //good to use MAX_PATH for fns
	CString fn;
	stringstream ss;
	string fp;
	
	//Get current exe directory
	GetModuleFileNameA(NULL, buffer, sizeof(buffer));
	fn = CString(buffer);
	
	//convert char* to string
	ss << buffer;
	ss >> fp;

	//Truncate to the '*\native' folder
	fn = fn.Left(fp.rfind("native")+6); //find the start of the word native and move position to the end of the word
	CT2CA convertedString(fn);
	string filepath(convertedString);
	filepath.append("\\output\\"); //places output in the svndir\native\output folder
    
    oss << filepath << "nocl-" << NUMPARTICLES << "_" << time(NULL) << ".csv";
	myfile.open( oss.str() );

	//oss << "C:/log" << NUMPARTICLES << "nocl.csv";
#endif
    
#ifdef __APPLE__
    char path[FILENAME_MAX];
    uint32_t size = sizeof(path);
    _NSGetExecutablePath(path, &size);
    
    stringstream ss;
    string fp;
    
    ss << path;
    ss >> fp;
    
    //cout << fp << " | " << fp.rfind("native") << endl;
    string fn = genFileName();
    fp = fp.substr(0, fp.rfind(outputParent)+outputParent.length()) + "/" + outputDir + "/" + fn;
    //cout << fp << endl;
    
    myfile.open(fp.c_str());
#endif

	if (!myfile.is_open()) { //verify file was successfully opened
		cout << "Error opening output file" << endl;
#ifdef WIN32
        cin.get();
#endif
		Quit();
	}
	myfile << "Total Time: " << endTime - startTime << "\n";
	myfile << "index,timeVal,type\n"; //add column headers
	for(int i = 0; i < TIME_COUNT; i++) //loop through the computed times and add each set to the results file
	{
		switch(i%3) {
			case 0:
				myfile << i << "," << compTimes[i] << ",compute\n";
				break;
			case 1:
				myfile << i << "," << compTimes[i] << ",transfer\n";
				break;
			case 2:
				myfile << i << "," << compTimes[i] << ",display\n";
				break;
		}
	}
	myfile.close();
    
    
	Quit();
}

// Return the number of seconds since the start of the program:
float ElapsedSeconds( void )
{
	// # of milliseconds since the start of the program
	int ms = glutGet( GLUT_ELAPSED_TIME );
	
	// Convert to seconds:
	return (float)ms / 1000.;
}

// Initialize the glui window
void InitGlui( void )
{
	GLUI_Panel *panel;
	GLUI_Rotation *rot;
	GLUI_Translation *trans/*, *scale*/;
	
	// Set up the glui window
	glutInitWindowPosition( INIT_WINDOW_SIZE + 50, 0 );
	Glui = GLUI_Master.create_glui( (char *) GLUITITLE );
		
	Glui->add_statictext( (char *) GLUITITLE );
	Glui->add_separator();
	
	Glui->add_checkbox( "Perspective", &WhichProjection );
	
	panel = Glui->add_panel( "Object Transformation" );
	
	rot = Glui->add_rotation_to_panel( panel, "Rotation", (float *) RotMatrix );
	
	// Allow the object to be spun via the glui rotation widget
	rot->set_spin( 1.0 );
	
	/*Glui->add_column_to_panel( panel, GLUIFALSE );
	scale = Glui->add_translation_to_panel( panel, "Scale",  GLUI_TRANSLATION_Y , &Scale2 );
	scale->set_speed( 0.01f );
	
	Glui->add_column_to_panel( panel, GLUIFALSE );
	trans = Glui->add_translation_to_panel( panel, "Trans XY", GLUI_TRANSLATION_XY, &TransXYZ[0] );
	trans->set_speed( 1.1f );*/
	
	Glui->add_column_to_panel( panel, GLUIFALSE );
	trans = Glui->add_translation_to_panel( panel, "Trans Z",  GLUI_TRANSLATION_Z , &TransXYZ[2] );
	trans->set_speed( 0.4f );
	
	//Glui->add_checkbox( "Debug", &Debug );
	
	panel = Glui->add_panel( "", GLUIFALSE );
	
	Glui->add_button_to_panel( panel, "Reset", RESET, (GLUI_Update_CB) Buttons );
	Glui->add_column_to_panel( panel, GLUIFALSE );
	Glui->add_button_to_panel( panel, "Go !", GO, (GLUI_Update_CB) Buttons );
	Glui->add_column_to_panel( panel, GLUIFALSE );
	Glui->add_button_to_panel( panel, "Quit", QUIT, (GLUI_Update_CB) Buttons );
	
	// Window to post a redisplay to
	Glui->set_main_gfx_window( MainWindow );
	
	// Set idle function:
	GLUI_Master.set_glutIdleFunc( NULL );
	
	Scale = 1.0;
}


// Initialize the glut and OpenGL libraries
//	Also setup display lists and callback functions

void InitGraphics( void )
{	
	// setup the display mode:
	// ( *must* be done before call to glutCreateWindow() )
	// ask for color, double-buffering, and z-buffering:
	
	glutInitDisplayMode( GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH );
	
	// set the initial window configuration:
	glutInitWindowPosition( 0, 0 );
	glutInitWindowSize( INIT_WINDOW_SIZE, INIT_WINDOW_SIZE );
		
	// open the window and set its title:	
	MainWindow = glutCreateWindow( WINDOWTITLE );
	glutSetWindowTitle( WINDOWTITLE );
		
	// setup the clear values:	
	glClearColor( BACKCOLOR[0], BACKCOLOR[1], BACKCOLOR[2], BACKCOLOR[3] );	
	
	// Set up the callback routines
	
	// DisplayFunc -- redraw the window
	// ReshapeFunc -- handle the user resizing the window
	// KeyboardFunc -- handle a keyboard input
	// MouseFunc -- handle the mouse button going down or up
	// MotionFunc -- handle the mouse moving with a button down
	// PassiveMotionFunc -- handle the mouse moving with a button up
	// VisibilityFunc -- handle a change in window visibility
	// EntryFunc	-- handle the cursor entering or leaving the window
	// SpecialFunc -- handle special keys on the keyboard
	// SpaceballMotionFunc -- handle spaceball translation
	// SpaceballRotateFunc -- handle spaceball rotation
	// SpaceballButtonFunc -- handle spaceball button hits
	// ButtonBoxFunc -- handle button box hits
	// DialsFunc -- handle dial rotations
	// TabletMotionFunc -- handle digitizing tablet motion
	// TabletButtonFunc -- handle digitizing tablet button hits
	// MenuStateFunc -- declare when a pop-up menu is in use
	// TimerFunc -- trigger something to happen a certain time from now
	// IdleFunc -- what to do when nothing else is going on
	
	glutSetWindow( MainWindow );
	glutDisplayFunc( Display );
	glutReshapeFunc( Resize );
	glutKeyboardFunc( Keyboard );
	glutMouseFunc( MouseButton );
	glutMotionFunc( MouseMotion );
	glutPassiveMotionFunc( NULL );
	glutVisibilityFunc( Visibility );
	glutEntryFunc( NULL );
	glutSpecialFunc( NULL );
	glutSpaceballMotionFunc( NULL );
	glutSpaceballRotateFunc( NULL );
	glutSpaceballButtonFunc( NULL );
	glutButtonBoxFunc( NULL );
	glutDialsFunc( NULL );
	glutTabletMotionFunc( NULL );
	glutTabletButtonFunc( NULL );
	glutMenuStateFunc( NULL );
	glutTimerFunc( 0, NULL, 0 );
	
	#ifdef WIN32
	GLenum err = glewInit();
	if( err != GLEW_OK )
	{
		fprintf( stderr, "glewInit Error\n" );
	}
	#endif

	glMatrixMode(GL_MODELVIEW);
	
	glGenBuffers( 1, &hPobj );
	glBindBuffer( GL_ARRAY_BUFFER, hPobj );
	glBufferData( GL_ARRAY_BUFFER, 4 * NUMPARTICLES * sizeof(float), NULL, GL_DYNAMIC_DRAW );

	glGenBuffers( 1, &hCobj );
	glBindBuffer( GL_ARRAY_BUFFER, hCobj );
	glBufferData( GL_ARRAY_BUFFER, 4 * NUMPARTICLES * sizeof(float), NULL, GL_STATIC_DRAW );

	glBindBuffer( GL_ARRAY_BUFFER, 0 );

	initParticles();
}

// Initialize the display lists that will not change:
void InitLists( void ) { }


// Keyboard callback:
void Keyboard( unsigned char c, int x, int y )
{	
	switch( c )
	{
		case 'o':
		case 'O':
			WhichProjection = ORTHO;
			break;
			
		case 'p':
		case 'P':
			WhichProjection = PERSP;
			break;
			
		case 'q':
		case 'Q':
		case ESCAPE:
			Buttons( QUIT );	
			break;			
	
		case 'r':
		case 'R':
			LeftButton = ROTATE;
			break;
			
		case 's':
		case 'S':
			LeftButton = SCALE;
			break;
			
		default:
			fprintf( stderr, "Don't know what to do with keyboard hit: '%c' (0x%0x)\n", c, c );
	}
	
	
	// Synchronize live variables
	Glui->sync_live();
	
	
	// Force a call to Display():
	glutSetWindow( MainWindow );
	glutPostRedisplay();
}


// Mouse button callback
void MouseButton( int button, int state, int x, int y )
{
	int b;			// LEFT, MIDDLE, or RIGHT
	
	// Get the proper button bit mask:
	switch( button )
	{
		case GLUT_LEFT_BUTTON:
			b = LEFT;		break;
			
		case GLUT_MIDDLE_BUTTON:
			b = MIDDLE;		break;
			
		case GLUT_RIGHT_BUTTON:
			b = RIGHT;		break;
			
		default:
			b = 0;
			fprintf( stderr, "Unknown mouse button: %d\n", button );
	}
	
	if( state == GLUT_DOWN )
	{
		Xmouse = x;
		Ymouse = y;
		ActiveButton |= b;
	}
	else
	{
		ActiveButton &= ~b;
	}
}


// Mouse movement callback
void MouseMotion( int x, int y )
{
	int dx, dy;		// change in mouse coordinates
	
	dx = x - Xmouse;		// change in mouse coords
	dy = y - Ymouse;
	
	if( ActiveButton & LEFT )
	{
		switch( LeftButton )
		{
			case ROTATE:
				Xrot += ( ANGFACT*dy );
				Yrot += ( ANGFACT*dx );
				break;
				
			case SCALE:
				Scale += SCLFACT * (float) ( dx - dy );
				if( Scale < MINSCALE )
					Scale = MINSCALE;
				break;
		}
	}
	
	if( ActiveButton & MIDDLE )
	{
		Scale += SCLFACT * (float) ( dx - dy );
		
		// Keep object from turning inside-out or disappearing:
		if( Scale < MINSCALE )
			Scale = MINSCALE;
	}
	
	// New current position
	Xmouse = x;			
	Ymouse = y;
	
	glutSetWindow( MainWindow );
	glutPostRedisplay();
}


// reset everything
	// only sets global variables --
	// the glut main loop is responsible for redrawing the scene
void Reset( void )
{
	ActiveButton = 0;
	LeftButton = ROTATE;
	Scale  = 1.0;
	Scale2 = 20.0;		// because add 1. to it in Display()
	WhichProjection = PERSP;
	//Xrot = Yrot = 20.;
	Xrot = Yrot = 0.;
	TransXYZ[0] = TransXYZ[1] = TransXYZ[2] = 0.;
	
					  RotMatrix[0][1] = RotMatrix[0][2] = RotMatrix[0][3] = 0.;
	RotMatrix[1][0]                   = RotMatrix[1][2] = RotMatrix[1][3] = 0.;
	RotMatrix[2][0] = RotMatrix[2][1]                   = RotMatrix[2][3] = 0.;
	RotMatrix[3][0] = RotMatrix[3][1] = RotMatrix[3][3]                   = 0.;
	RotMatrix[0][0] = RotMatrix[1][1] = RotMatrix[2][2] = RotMatrix[3][3] = 1.;
	
	initParticles();
}

void Resize( int width, int height )
{
	glutSetWindow( MainWindow );
	glutPostRedisplay();
}

void Visibility ( int state )
{	
	if( state == GLUT_VISIBLE )
	{
		glutSetWindow( MainWindow );
		glutPostRedisplay();
	}
	else
	{
		// could optimize by keeping track of the fact
		// that the window is not visible and avoid
		// animating or redrawing it ...
	}
}

void Quit()
{
	exit( 0 );
}

float myRandom(int i, float low, float high)
{
	float a = 16645.0;
	float c = 10139042.0;
	float m = 42949672.0;

	randomArray[i] = fmod((a * randomArray[i] + c), m); //since % doesn't work on floats
	return ((high - low) * (randomArray[i] / m)) + low;
}

//Returns a velocity to apply in X and Z
float adjustVelocity(int i, float low, float high)
{
	return (float)((((((2 * myRandom(i, low, high)) + 1) * myRandom(i, low, high)) + 1) * 0.005) - (((((2 * myRandom(i, low, high)) + 1) * myRandom(i, low, high)) + 1) * 0.005))/50;
}

//Returns an Acceleration to apply in Y
float adjustAccel(int i, float low, float high)
{
	return (float)(((((5 * myRandom(i, low, high)) + 5) * myRandom(i, low, high)) + 1) * 0.02)/100;
}

//Return a Ranfom color in the specified range (0-255) for a single channel
int randomColor(int min, int max)
{
	//return Ranf(min,max)/255;
	return min + (rand() % (int)(max - min + 1));
}


void updateParticle(int i)
{	
	points[i].y = points[i].y + hVel[i].y - hVel[i].w;
	hVel[i].w = hVel[i].w + 0.0015f;
			
	points[i].x = points[i].x + hVel[i].x;
	points[i].z = points[i].z + hVel[i].z;
}

void genParticle(int i, bool nonMoveAttrib)
{
	points[i].x = 0.0f;
	points[i].y = 0.0f;
	points[i].z = 0.0f;

	hVel[i].x = adjustVelocity(i, 2, 14);
	hVel[i].z = adjustVelocity(i, 2, 14);
				
	hVel[i].y = adjustAccel(i, 2, 11);
	hVel[i].w = 0.0015f;

	if (nonMoveAttrib == true) {
		colors[i].r = ((float)randomColor(0,255))/255.0f;
		colors[i].g = ((float)randomColor(0,80))/255.0f;
		colors[i].b = ((float)randomColor(0,80))/255.0f;
		colors[i].a = 1.0f;
	}
}

void initParticles() {
	// Set the particles back to their initial characteristics
	//glBindBuffer( GL_ARRAY_BUFFER, hPobj );
	//struct xyzw *points = (struct xyzw *) glMapBuffer( GL_ARRAY_BUFFER, GL_WRITE_ONLY );
	for( int i = 0; i < NUMPARTICLES; i++ )
	{
		points[i].x = startingPos[0];
		points[i].y = startingPos[1];
		points[i].z = startingPos[2];
		points[i].w = 1.;
	}
	//glUnmapBuffer( GL_ARRAY_BUFFER );

	//glBindBuffer( GL_ARRAY_BUFFER, hCobj );
	//struct rgba *colors = (struct rgba *) glMapBuffer( GL_ARRAY_BUFFER, GL_WRITE_ONLY );
	for( int i = 0; i < NUMPARTICLES; i++ )
	{
		colors[i].r = ((float)randomColor(0,255))/255.0f;
		colors[i].g = ((float)randomColor(0,80))/255.0f;
		colors[i].b = ((float)randomColor(0,80))/255.0f;
		colors[i].a = 1.0f;
	}
	//glUnmapBuffer( GL_ARRAY_BUFFER );

	for( int i = 0; i < NUMPARTICLES; i++ )
	{
		randomArray[i] = i;
		hVel[i].x = adjustVelocity(i, 2, 14);
		hVel[i].y = adjustAccel(i, 2, 11);
		hVel[i].z = adjustVelocity(i, 2, 14);
		hVel[i].w = 0.0015f;
	}

	glBindBuffer( GL_ARRAY_BUFFER, hPobj );
	glBufferData( GL_ARRAY_BUFFER, 4 * NUMPARTICLES * sizeof(float), (void*)points, GL_DYNAMIC_DRAW);
	glBindBuffer( GL_ARRAY_BUFFER, hCobj );
	glBufferData( GL_ARRAY_BUFFER, 4 * NUMPARTICLES * sizeof(float), (void*)colors, GL_DYNAMIC_DRAW);
	//fprintf(stdout, "particle init done");
}